<script lang="ts">
  import { Check, Copy } from '@gradio/icons';
  import type { SelectData } from '@gradio/utils';
  import {
    afterUpdate,
    beforeUpdate,
    createEventDispatcher,
    tick,
  } from 'svelte';

  export let value = '';
  export let value_is_output = false;
  export let lines = 1;
  export let placeholder = '';
  export let disabled = false;
  export let show_label = true;
  export let container = true;
  export let max_lines: number;
  export let type: 'text' | 'password' | 'email' = 'text';
  export let show_copy_button = false;
  export let rtl = false;
  export let autofocus = false;
  export let text_align: 'left' | 'right' | undefined = undefined;
  export let autoscroll = true;

  let el: HTMLTextAreaElement | HTMLInputElement;
  let copied = false;
  let timer: number;
  let can_scroll: boolean;
  let previous_scroll_top = 0;
  let user_has_scrolled_up = false;

  $: (value, el && lines !== max_lines && resize({ target: el }));

  $: if (value === null) value = '';

  const dispatch = createEventDispatcher<{
    change: string;
    submit: undefined;
    blur: undefined;
    select: SelectData;
    input: undefined;
    focus: undefined;
  }>();

  beforeUpdate(() => {
    can_scroll = el && el.offsetHeight + el.scrollTop > el.scrollHeight - 100;
  });

  const scroll = (): void => {
    if (can_scroll && autoscroll && !user_has_scrolled_up) {
      el.scrollTo(0, el.scrollHeight);
    }
  };

  function handle_change(): void {
    dispatch('change', value);
    if (!value_is_output) {
      dispatch('input');
    }
  }
  afterUpdate(() => {
    if (autofocus) {
      el.focus();
    }
    if (can_scroll && autoscroll) {
      scroll();
    }
    value_is_output = false;
  });
  $: (value, handle_change());

  async function handle_copy(): Promise<void> {
    if ('clipboard' in navigator) {
      await navigator.clipboard.writeText(value);
      copy_feedback();
    }
  }

  function copy_feedback(): void {
    copied = true;
    if (timer) clearTimeout(timer);
    timer = setTimeout(() => {
      copied = false;
    }, 1000) as unknown as number;
  }

  function handle_select(event: Event): void {
    const target: HTMLTextAreaElement | HTMLInputElement = event.target as
      | HTMLTextAreaElement
      | HTMLInputElement;
    const text = target.value;
    const index: [number, number] = [
      target.selectionStart as number,
      target.selectionEnd as number,
    ];
    dispatch('select', { value: text.substring(...index), index: index });
  }

  function handle_focus(): void {
    dispatch('focus');
  }
  function handle_blur(): void {
    dispatch('blur');
  }

  async function handle_keypress(e: KeyboardEvent): Promise<void> {
    await tick();
    if (e.key === 'Enter' && e.shiftKey && lines > 1) {
      e.preventDefault();
      dispatch('submit');
    } else if (
      e.key === 'Enter' &&
      !e.shiftKey &&
      lines === 1 &&
      max_lines >= 1
    ) {
      e.preventDefault();
      dispatch('submit');
    }
  }

  function handle_scroll(event: Event): void {
    const target = event.target as HTMLElement;
    const current_scroll_top = target.scrollTop;
    if (current_scroll_top < previous_scroll_top) {
      user_has_scrolled_up = true;
    }
    previous_scroll_top = current_scroll_top;

    const max_scroll_top = target.scrollHeight - target.clientHeight;
    const user_has_scrolled_to_bottom = current_scroll_top >= max_scroll_top;
    if (user_has_scrolled_to_bottom) {
      user_has_scrolled_up = false;
    }
  }

  // resize textarea height
  async function resize(
    event: Event | { target: HTMLTextAreaElement | HTMLInputElement }
  ): Promise<void> {
    await tick();
    if (lines === max_lines) return;
    const max =
      max_lines === undefined
        ? false
        : max_lines === undefined // default
          ? 21 * 11
          : 21 * (max_lines + 1);
    const min = 21 * (lines + 1);

    const target = event.target as HTMLTextAreaElement;
    target.style.height = '1px';

    let scroll_height;
    if (max && target.scrollHeight > max) {
      scroll_height = max;
    } else if (target.scrollHeight < min) {
      scroll_height = min;
    } else {
      scroll_height = target.scrollHeight;
    }

    target.style.height = `${scroll_height}px`;
  }

  function text_area_resize(
    _el: HTMLTextAreaElement,
    _value: string
  ): any | undefined {
    if (lines === max_lines) return;
    _el.style.overflowY = 'scroll';
    _el.addEventListener('input', resize);

    if (!_value.trim()) return;
    resize({ target: _el });

    return {
      destroy: () => _el.removeEventListener('input', resize),
    };
  }
</script>

<!-- svelte-ignore a11y-autofocus -->
<label class:container>
  {#if show_label && show_copy_button}
    {#if copied}
      <button aria-label="Copied" aria-roledescription="Text copied">
        <Check />
      </button>
    {:else}
      <button
        on:click={handle_copy}
        aria-label="Copy"
        aria-roledescription="Copy text"
      >
        <Copy />
      </button>
    {/if}
  {/if}

  {#if lines === 1 && max_lines === 1}
    {#if type === 'text'}
      <input
        type="text"
        class="scroll-hide"
        dir={rtl ? 'rtl' : 'ltr'}
        bind:value
        bind:this={el}
        {placeholder}
        {disabled}
        {autofocus}
        on:keypress={handle_keypress}
        on:blur={handle_blur}
        on:select={handle_select}
        on:focus={handle_focus}
        style={text_align ? 'text-align: ' + text_align : ''}
      />
    {:else if type === 'password'}
      <input
        type="password"
        class="scroll-hide"
        bind:value
        bind:this={el}
        {placeholder}
        {disabled}
        {autofocus}
        on:keypress={handle_keypress}
        on:blur={handle_blur}
        on:select={handle_select}
        on:focus={handle_focus}
        autocomplete=""
      />
    {:else if type === 'email'}
      <input
        type="email"
        class="scroll-hide"
        bind:value
        bind:this={el}
        {placeholder}
        {disabled}
        {autofocus}
        on:keypress={handle_keypress}
        on:blur={handle_blur}
        on:select={handle_select}
        on:focus={handle_focus}
        autocomplete="email"
      />
    {/if}
  {:else}
    <textarea
      use:text_area_resize={value}
      class="scroll-hide"
      dir={rtl ? 'rtl' : 'ltr'}
      bind:value
      bind:this={el}
      {placeholder}
      rows={lines}
      {disabled}
      {autofocus}
      on:keypress={handle_keypress}
      on:blur={handle_blur}
      on:select={handle_select}
      on:focus={handle_focus}
      on:scroll={handle_scroll}
      style={text_align ? 'text-align: ' + text_align : ''}
    ></textarea>
  {/if}
</label>

<style>
  label {
    display: block;
    width: 100%;
  }

  input,
  textarea {
    display: block;
    position: relative;
    outline: none !important;
    box-shadow: var(--input-shadow);
    background: var(--input-background-fill);
    padding: var(--input-padding);
    width: 100%;
    color: var(--body-text-color);
    font-weight: var(--input-text-weight);
    font-size: var(--input-text-size);
    line-height: var(--line-sm);
    border: none;
  }
  label:not(.container),
  label:not(.container) input,
  label:not(.container) textarea {
    height: 100%;
  }
  input,
  textarea {
    border: var(--input-border-width) solid var(--input-border-color);
    border-radius: var(--input-radius);
  }
  input:disabled,
  textarea:disabled {
    -webkit-text-fill-color: var(--body-text-color);
    -webkit-opacity: 0.5;
    opacity: 0.5;
  }

  input:focus,
  textarea:focus {
    box-shadow: var(--input-shadow-focus);
    border-color: var(--input-border-color-focus);
  }

  input::placeholder,
  textarea::placeholder {
    color: var(--input-placeholder-color);
  }
  button {
    display: flex;
    position: absolute;
    top: var(--block-label-margin);
    right: var(--block-label-margin);
    align-items: center;
    box-shadow: var(--shadow-drop);
    border: 1px solid var(--color-border-primary);
    border-top: none;
    border-right: none;
    border-radius: var(--block-label-right-radius);
    background: var(--block-label-background-fill);
    padding: 5px;
    width: 22px;
    height: 22px;
    overflow: hidden;
    color: var(--block-label-color);
    font: var(--font-sans);
    font-size: var(--button-small-text-size);
  }
</style>
